﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace CustomVirtualizingPanel
{
    /// <summary>
    /// 虚拟化分页容器
    /// </summary>
    public sealed class VirtualizingPagerPanel : VirtualizingPanel
    {
        #region Field

        private readonly List<UIElement> _currentShowChildren;          // 当前显示的子元素
        private int _horizontalChildMaxCount;                           // 水平方向显示的子元素最大个数
        private int _verticalChildMaxCount;                             // 垂直方向显示的子元素最大个数
        private int _firstVisibleChildIndex = -1;                       // 第一个显示的子元素索引
        private int _lastVisibleChildIndex = -1;                        // 最后一个显示的子元素索引

        #endregion

        #region Dependency Property

        public static readonly DependencyProperty ChildrenCountProperty = DependencyProperty.Register(
            "ChildrenCount", typeof(int), typeof(VirtualizingPagerPanel));

        public static readonly DependencyProperty PageSizeProperty = DependencyProperty.Register(
            "PageSize", typeof(int), typeof(VirtualizingPagerPanel));

        public static readonly DependencyProperty PageIndexProperty = DependencyProperty.Register(
            "PageIndex", typeof(int), typeof(VirtualizingPagerPanel),
            new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure));

        public static readonly DependencyProperty ChildWidthProperty = DependencyProperty.Register(
            "ChildWidth", typeof(double), typeof(VirtualizingPagerPanel),
            new FrameworkPropertyMetadata(30d, FrameworkPropertyMetadataOptions.AffectsMeasure)
            , new ValidateValueCallback(IsWidthHeightValid));

        public static readonly DependencyProperty ChildHeightProperty = DependencyProperty.Register(
            "ChildHeight", typeof(double), typeof(VirtualizingPagerPanel),
            new FrameworkPropertyMetadata(30d, FrameworkPropertyMetadataOptions.AffectsMeasure)
            , new ValidateValueCallback(IsWidthHeightValid));

        private static bool IsWidthHeightValid(object value)
        {
            var v = (double)value;
            return !double.IsNaN(v) && !Double.IsPositiveInfinity(v) && v > 0d;
        }

        #endregion

        #region Construct

        /// <summary>
        /// 实例化一个虚拟化分页容器
        /// </summary>
        public VirtualizingPagerPanel()
        {
            _currentShowChildren = new List<UIElement>();
        }

        #endregion

        #region Property

        /// <summary>
        /// 子元素个数
        /// </summary>
        public int ChildrenCount
        {
            get { return (int)GetValue(ChildrenCountProperty); }
            private set { SetValue(ChildrenCountProperty, value); }
        }

        /// <summary>
        /// 页码
        /// </summary>
        public int PageIndex
        {
            get { return (int)GetValue(PageIndexProperty); }
            set { SetValue(PageIndexProperty, value); }
        }

        /// <summary>
        /// 子元素高度
        /// </summary>
        public double ChildHeight
        {
            get { return (double)GetValue(ChildHeightProperty); }
            set { SetValue(ChildHeightProperty, value); }
        }

        /// <summary>
        /// 子元素宽度
        /// </summary>
        public double ChildWidth
        {
            get { return (double)GetValue(ChildWidthProperty); }
            set { SetValue(ChildWidthProperty, value); }
        }

        /// <summary>
        /// 子元素显示最大个数
        /// </summary>
        public int PageSize
        {
            get { return (int)GetValue(PageSizeProperty); }
            private set { SetValue(PageSizeProperty, value); }
        }

        /// <summary>
        /// 当前显示的子元素
        /// </summary>
        public ReadOnlyCollection<UIElement> CurrentShowChildren
        {
            get { return new ReadOnlyCollection<UIElement>(_currentShowChildren); }
        }

        #endregion

        #region Protected Override Method

        protected override Size MeasureOverride(Size availableSize)
        {
            if (availableSize.IsEmpty || double.IsInfinity(availableSize.Height) || double.IsInfinity(availableSize.Width))
            {
                throw new Exception(string.Format("PagerPanel不支持该尺寸下的布局, availableSize:{0}", availableSize));
            }

            if (ChildWidth > availableSize.Width || ChildHeight > availableSize.Height)
            {
                throw new Exception(string.Format("子元素尺寸不应大于布局尺寸, availableSize:{0},ChildWidth:{1}, ChildHeight: {2} ", availableSize, ChildWidth, ChildHeight));
            }

            ComputeVisibleChildIndex(availableSize, out _firstVisibleChildIndex, out _lastVisibleChildIndex);

            MeasureChild(availableSize, _firstVisibleChildIndex, _lastVisibleChildIndex);

            // 清除不显示的子元素(这里可以考虑在系统空闲时清除子元素)
            CleanUpItems(_firstVisibleChildIndex, _lastVisibleChildIndex);

            return availableSize;
        }

        /// <summary>
        /// 计算可是元素起止索引
        /// </summary>
        /// <param name="availableSize">可用布局尺寸</param>
        /// <param name="firstVisibleChildIndex">第一个显示的子元素索引</param>
        /// <param name="lastVisibleChildIndex">最后一个显示的子元素索引</param>
        private void ComputeVisibleChildIndex(Size availableSize, out int firstVisibleChildIndex, out int lastVisibleChildIndex)
        {
            ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);

            if (itemsControl != null && itemsControl.Items != null && ChildWidth > 0 && ChildHeight > 0)
            {
                ChildrenCount = itemsControl.Items.Count;

                _horizontalChildMaxCount = (int)(availableSize.Width / ChildWidth);
                _verticalChildMaxCount = (int)(availableSize.Height / ChildHeight);

                PageSize = _horizontalChildMaxCount * _verticalChildMaxCount;

                // 计算子元素显示起止索引
                firstVisibleChildIndex = PageIndex * PageSize;
                lastVisibleChildIndex = Math.Min(ChildrenCount, firstVisibleChildIndex + PageSize) - 1;

                Debug.WriteLine("firstVisibleChildIndex:{0}, lastVisibleChildIndex{1}", firstVisibleChildIndex, lastVisibleChildIndex);
            }
            else
            {
                ChildrenCount = 0;
                firstVisibleChildIndex = -1;
                lastVisibleChildIndex = -1;
                PageSize = 0;
            }
        }

        /// <summary>
        /// 测量子元素布局,生成需要显示的子元素
        /// </summary>
        /// <param name="availableSize">可用布局尺寸</param>
        /// <param name="firstVisibleChildIndex">第一个显示的子元素索引</param>
        /// <param name="lastVisibleChildIndex">最后一个显示的子元素索引</param>
        private void MeasureChild(Size availableSize, int firstVisibleChildIndex, int lastVisibleChildIndex)
        {
            if (firstVisibleChildIndex < 0)
            {
                return;
            }

            // 注意，在第一次使用 ItemContainerGenerator之前要先访问一下InternalChildren, 
            // 否则ItemContainerGenerator为null，是一个Bug
            UIElementCollection children = InternalChildren;
            IItemContainerGenerator generator = ItemContainerGenerator;

            // 获取第一个可视元素位置信息
            GeneratorPosition position = generator.GeneratorPositionFromIndex(firstVisibleChildIndex);
            // 根据元素位置信息计算子元素索引
            int childIndex = position.Offset == 0 ? position.Index : position.Index + 1;

            using (generator.StartAt(position, GeneratorDirection.Forward, true))
            {
                for (int itemIndex = firstVisibleChildIndex; itemIndex <= lastVisibleChildIndex; itemIndex++, childIndex++)
                {
                    bool isNewlyRealized;   // 用以指示新生成的元素是否是新实体化的

                    // 生成下一个子元素
                    var child = (UIElement)generator.GenerateNext(out isNewlyRealized);

                    if (isNewlyRealized)
                    {
                        if (childIndex >= children.Count)
                        {
                            AddInternalChild(child);
                        }
                        else
                        {
                            InsertInternalChild(childIndex, child);
                        }
                        generator.PrepareItemContainer(child);
                    }

                    // 测算子元素布局
                    child.Measure(availableSize);
                }
            }
        }

        /// <summary>
        /// 清理不需要显示的子元素
        /// </summary>
        /// <param name="firstVisibleChildIndex">第一个显示的子元素索引</param>
        /// <param name="lastVisibleChildIndex">最后一个显示的子元素索引</param>
        private void CleanUpItems(int firstVisibleChildIndex, int lastVisibleChildIndex)
        {
            UIElementCollection children = this.InternalChildren;
            IItemContainerGenerator generator = this.ItemContainerGenerator;

            // 清除不需要显示的子元素，注意从集合后向前操作，以免造成操作过程中元素索引发生改变
            for (int i = children.Count - 1; i > -1; i--)
            {
                // 通过已显示的子元素的位置信息得出元素索引
                var childGeneratorPos = new GeneratorPosition(i, 0);
                int itemIndex = generator.IndexFromGeneratorPosition(childGeneratorPos);

                // 移除不再显示的元素
                if (itemIndex < firstVisibleChildIndex || itemIndex > lastVisibleChildIndex)
                {
                    generator.Remove(childGeneratorPos, 1);
                    RemoveInternalChildRange(i, 1);
                }
            }
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            // 安排子元素布局
            ArrangeChild(_horizontalChildMaxCount, _firstVisibleChildIndex, _lastVisibleChildIndex);

            return finalSize;
        }

        /// <summary>
        /// 安排子元素在容器中的位置
        /// </summary>
        /// <param name="horizontalChildMaxCount">水平显示子元素最大个数</param>
        /// <param name="firstVisibleChildIndex">第一个显示的子元素索引</param>
        /// <param name="lastVisibleChildIndex">最后一个显示的子元素索引</param>
        private void ArrangeChild(int horizontalChildMaxCount, int firstVisibleChildIndex, int lastVisibleChildIndex)
        {
            if (horizontalChildMaxCount < 1 || firstVisibleChildIndex < 0 || lastVisibleChildIndex < 0)
            {
                return;
            }

            _currentShowChildren.Clear();

            for (int index = firstVisibleChildIndex; index <= lastVisibleChildIndex; index++)
            {
                if (index < firstVisibleChildIndex || index > lastVisibleChildIndex)
                {
                    continue;
                }

                // 计算子元素坐标
                int x = (index - firstVisibleChildIndex) / horizontalChildMaxCount;
                int y = (index - firstVisibleChildIndex) - x * horizontalChildMaxCount;

                UIElement child = InternalChildren[index - firstVisibleChildIndex];
                // 将坐标换算成容器中对应布局区域信息
                var rec = new Rect(new Point(y * ChildWidth, x * ChildHeight), new Size(ChildWidth, ChildHeight));

                child.Arrange(rec);
                _currentShowChildren.Add(child);
            }
        }

        #endregion
    }
}
